All files / src/api error.ts

76.6% Statements 36/47
53.33% Branches 16/30
100% Functions 4/4
75.56% Lines 34/45
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 72 73 74 75 76 77 78 79 80 81 82 83 84 85 86 87 88 89 90 91 92 93 94 95 96 97 98 99 100 101 102 103 104 105 106 107 108 109 110 111 112 113 114 115 116 117 118 119 120 121 122 123 124 125 126 127 128 129 130 131 132 133 134 135 136 137 138 139 140 141 142 143 144 145 146 147 148 149 150 151 152 153 154 155 156 157 158 159 160 161 162 163 164 165 166 167 168 169 170 171 172 173                                                    3x                                               3x                         5x         5x   5x 5x   3x                       11x 7x   4x     1x   3x                                                       3x         11x     11x   11x     11x 11x 9x 2x 2x 2x   1x   1x       1x   1x 1x 1x     1x 1x 1x             10x       7x     3x    
/**
 * Copyright 2017 Google Inc.
 *
 * Licensed under the Apache License, Version 2.0 (the "License");
 * you may not use this file except in compliance with the License.
 * You may obtain a copy of the License at
 *
 *   http://www.apache.org/licenses/LICENSE-2.0
 *
 * Unless required by applicable law or agreed to in writing, software
 * distributed under the License is distributed on an "AS IS" BASIS,
 * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
 * See the License for the specific language governing permissions and
 * limitations under the License.
 */
 
import { HttpsError, FunctionsErrorCode } from '@firebase/functions-types';
import { Serializer } from '../serializer';
 
/**
 * Standard error codes for different ways a request can fail, as defined by:
 * https://github.com/googleapis/googleapis/blob/master/google/rpc/code.proto
 *
 * This map is used primarily to convert from a backend error code string to
 * a client SDK error code string, and make sure it's in the supported set.
 */
const errorCodeMap: { [name: string]: FunctionsErrorCode } = {
  OK: 'ok',
  CANCELLED: 'cancelled',
  UNKNOWN: 'unknown',
  INVALID_ARGUMENT: 'invalid-argument',
  DEADLINE_EXCEEDED: 'deadline-exceeded',
  NOT_FOUND: 'not-found',
  ALREADY_EXISTS: 'already-exists',
  PERMISSION_DENIED: 'permission-denied',
  UNAUTHENTICATED: 'unauthenticated',
  RESOURCE_EXHAUSTED: 'resource-exhausted',
  FAILED_PRECONDITION: 'failed-precondition',
  ABORTED: 'aborted',
  OUT_OF_RANGE: 'out-of-range',
  UNIMPLEMENTED: 'unimplemented',
  INTERNAL: 'internal',
  UNAVAILABLE: 'unavailable',
  DATA_LOSS: 'data-loss'
};
 
/**
 * An explicit error that can be thrown from a handler to send an error to the
 * client that called the function.
 */
export class HttpsErrorImpl extends Error implements HttpsError {
  /**
   * A standard error code that will be returned to the client. This also
   * determines the HTTP status code of the response, as defined in code.proto.
   */
  readonly code: FunctionsErrorCode;
 
  /**
   * Extra data to be converted to JSON and included in the error response.
   */
  readonly details?: any;
 
  constructor(code: FunctionsErrorCode, message?: string, details?: any) {
    super(message);
 
    // This is a workaround for a bug in TypeScript when extending Error:
    // tslint:disable-next-line
    // https://github.com/Microsoft/TypeScript-wiki/blob/master/Breaking-Changes.md#extending-built-ins-like-error-array-and-map-may-no-longer-work
    Object.setPrototypeOf(this, HttpsErrorImpl.prototype);
 
    this.code = code;
    this.details = details;
  }
}
 
/**
 * Takes an HTTP status code and returns the corresponding ErrorCode.
 * This is the standard HTTP status code -> error mapping defined in:
 * https://github.com/googleapis/googleapis/blob/master/google/rpc/code.proto
 *
 * @param status An HTTP status code.
 * @return The corresponding ErrorCode, or ErrorCode.UNKNOWN if none.
 */
function codeForHTTPStatus(status: number): FunctionsErrorCode {
  // Make sure any successful status is OK.
  if (status >= 200 && status < 300) {
    return 'ok';
  }
  switch (status) {
    case 0:
      // This can happen if the server returns 500.
      return 'internal';
    case 400:
      return 'invalid-argument';
    case 401:
      return 'unauthenticated';
    case 403:
      return 'permission-denied';
    case 404:
      return 'not-found';
    case 409:
      return 'aborted';
    case 429:
      return 'resource-exhausted';
    case 499:
      return 'cancelled';
    case 500:
      return 'internal';
    case 501:
      return 'unimplemented';
    case 503:
      return 'unavailable';
    case 504:
      return 'deadline-exceeded';
  }
  return 'unknown';
}
 
/**
 * Takes an HTTP response and returns the corresponding Error, if any.
 */
export function _errorForResponse(
  status: number,
  bodyJSON: any,
  serializer: Serializer
): Error | null {
  let code = codeForHTTPStatus(status);
 
  // Start with reasonable defaults from the status code.
  let description: string = code;
 
  let details: any = undefined;
 
  // Then look through the body for explicit details.
  try {
    const errorJSON = bodyJSON.error;
    if (errorJSON) {
      const status = errorJSON.status;
      Eif (typeof status === 'string') {
        if (!errorCodeMap[status]) {
          // They must've included an unknown error code in the body.
          return new HttpsErrorImpl('internal', 'internal');
        }
        code = errorCodeMap[status];
      }
      // TODO(klimt): Add better default descriptions for error enums.
      // The default description needs to be updated for the new code.
      description = status;
 
      const message = errorJSON.message;
      Eif (typeof message === 'string') {
        description = message;
      }
 
      details = errorJSON.details;
      Eif (details !== undefined) {
        details = serializer.decode(details);
      }
    }
  } catch (e) {
    // If we couldn't parse explicit error data, that's fine.
  }
 
  if (code === 'ok') {
    // Technically, there's an edge case where a developer could explicitly
    // return an error code of OK, and we will treat it as success, but that
    // seems reasonable.
    return null;
  }
 
  return new HttpsErrorImpl(code, description, details);
}